`ifndef EX_V
`define EX_V


`include "defines.v"

module ex(
	//from id_ex
	input  wire[`InstAddrWidth - 1 : 0] inst_addr_i,
	input  wire[`InstWidth - 1 : 0] 	inst_i,	
	input  wire[`RegAddrWidth - 1 : 0]  reg_waddr_i,	
	input  wire 	  					reg_wen_i,
	input  wire[`OPWidth - 1 : 0] 		op1_i,
	input  wire[`OPWidth - 1 : 0] 		op2_i,
	input  wire[`OPWidth - 1 : 0] 		op1_jump_i,
	input  wire[`OPWidth - 1 : 0] 		op2_jump_i,

	//to regs
	output reg[`RegAddrWidth - 1 : 0] 	waddr_o,
	output reg[`RegDataWidth - 1 : 0]	wdata_o,
	output reg 	    					wen_o,
	
	//to ctrl
	output reg[`InstAddrWidth - 1 : 0]	jump_addr_o,
	output reg   						jump_en_o,
	output reg  						hold_flag_o,

	// from mem
	input  wire[`MemDataWidth - 1 : 0]	mem_rdata_i,
	// to mem
	output reg							mem_wreq_o,
	output reg[3:0]						mem_wsel_o,
	output reg[`MemAddrWidth - 1 : 0]	mem_waddr_o,
	output reg[`MemDataWidth - 1 : 0]	mem_wdata_o
);

wire[6:0] opcode; 
wire[4:0] rd; 
wire[2:0] funct3; 
wire[4:0] rs1;
wire[4:0] rs2;
wire[6:0] funct7;
wire[4:0] shamt;

assign opcode = inst_i[6:0];
assign rd 	  = inst_i[11:7];
assign funct3 = inst_i[14:12];
assign rs1 	  = inst_i[19:15];
assign rs2 	  = inst_i[24:20];
assign funct7 = inst_i[31:25];
assign shamt  = inst_i[24:20];
	
// wire[`InstAddrWidth - 1 : 0] 	jump_imm;
wire  	   						op1_equal_op2;
wire							op1_less_op2_signed;
wire							op1_less_op2_unsigned;

// assign jump_imm = {{19{inst_i[31]}}, inst_i[31], inst_i[7], inst_i[30:25], inst_i[11:8], 1'b0};
assign op1_equal_op2 = (op1_i == op2_i) ? 1'b1 : 1'b0;
assign op1_less_op2_signed = ($signed(op1_i) < $signed(op2_i)) ? 1'b1 : 1'b0;
assign op1_less_op2_unsigned = (op1_i < op2_i) ? 1'b1 : 1'b0;

// ALU
wire[`OPWidth - 1 : 0] op1_add_op2;			// 加法器
wire[`OPWidth - 1 : 0] op1_and_op2;			// 与
wire[`OPWidth - 1 : 0] op1_xor_op2;			// 异或
wire[`OPWidth - 1 : 0] op1_or_op2;			// 或
wire[`OPWidth - 1 : 0] op1_shift_left_op2;	// 左移
wire[`OPWidth - 1 : 0] op1_shift_right_op2;	// 右移
wire[`OPWidth - 1 : 0] addr_add_addr_offset;// 加偏移地址

assign op1_add_op2 			= op1_i + op2_i;			
assign op1_and_op2 			= op1_i & op2_i;			
assign op1_xor_op2 			= op1_i ^ op2_i;			
assign op1_or_op2			= op1_i | op2_i;			
assign op1_shift_left_op2	= op1_i << op2_i;
assign op1_shift_right_op2	= op1_i >> op2_i;
assign addr_add_addr_offset = op1_jump_i + op2_jump_i;

// tpye I
wire[`OPWidth - 1 : 0] 			SRA_mask;
assign SRA_mask = (32'hFFFF_FFFF) >> op2_i[4:0];

// tpye store && load index
wire[1:0]  store_index 	= addr_add_addr_offset[1:0];
wire[1:0]  load_index 	= addr_add_addr_offset[1:0];

// type M
wire[`OPWidth - 1 : 0]	op1_invert;
wire[`OPWidth - 1 : 0]	op2_invert;
reg[`OPWidth - 1 : 0] 	mul_op1;
reg[`OPWidth - 1 : 0] 	mul_op2;
wire[63:0] mul_temp;
wire[63:0] mul_temp_invert;
assign op1_invert = ~op1_i + 1'b1;
assign op2_invert = ~op2_i + 1'b1;
assign mul_temp = mul_op1 * mul_op2;
assign mul_temp_invert = ~mul_temp + 1'b1;

// 乘法指令预处理
always @(*) begin
	if ((opcode == `INST_TYPE_M) && (funct7 == 7'b0000001)) begin
		case(funct3)
			`INST_MUL, `INST_MULHU: begin
				mul_op1 = op1_i;
				mul_op2 = op2_i;
			end
			`INST_MULHSU: begin
				mul_op1 = (op1_i[31] == 1'b1)? (op1_invert): op1_i;
				mul_op2 = op2_i;
			end
			`INST_MULH: begin
				mul_op1 = (op1_i[31] == 1'b1)? (op1_invert): op1_i;
				mul_op2 = (op2_i[31] == 1'b1)? (op2_invert): op2_i;
			end
			default: begin
				mul_op1 = op1_i;
				mul_op2 = op2_i;
			end
		endcase
	end
	else begin

	end
end

always @(*)begin
	case(opcode)
		`INST_TYPE_R, `INST_TYPE_M : begin
			if((funct7 == 7'b0000000) || (funct7 == 7'b0100000)) begin
				jump_addr_o = `INST_ZERO_ADDR;
				jump_en_o	= 1'b0;
				hold_flag_o = 1'b0;
				mem_wreq_o	= 1'b0;
				mem_wsel_o	= 4'h0;
				mem_waddr_o	= `MEM_ZERO_ADDR;
				mem_wdata_o = `MEM_ZERO_DATA;
				case(funct3)				
					`INST_ADD, `INST_SUB : begin
						if(funct7 == 7'b0000000) begin //add
							wdata_o = op1_add_op2;
							waddr_o = reg_waddr_i;
							wen_o 	= 1'b1;
						end
						else begin
							wdata_o = op1_i - op2_i;
							waddr_o = reg_waddr_i;
							wen_o  	= 1'b1; 								
						end
					end
					`INST_SLL : begin
						wdata_o = op1_shift_left_op2;
						waddr_o = reg_waddr_i;
						wen_o  	= 1'b1; 
					end
					`INST_SLT : begin
						wdata_o = {30'b0, op1_less_op2_signed};
						waddr_o = reg_waddr_i;
						wen_o 	= 1'b1;
					end
					`INST_SLTU : begin
						wdata_o = {30'b0, op1_less_op2_unsigned};
						waddr_o = reg_waddr_i;
						wen_o 	= 1'b1;
					end
					`INST_XOR : begin
						wdata_o = op1_xor_op2;
						waddr_o = reg_waddr_i;
						wen_o 	= 1'b1;
					end
					`INST_SRL, `INST_SRA : begin
						if(funct7 == 7'b0000000) begin // SRL
							wdata_o = op1_shift_right_op2;
							waddr_o = reg_waddr_i;
							wen_o  	= 1'b1; 
						end
						else begin
							wdata_o = ((op1_shift_right_op2) & SRA_mask) | ({32{op1_i[31]}} & (~SRA_mask));
							waddr_o = reg_waddr_i;
							wen_o  	= 1'b1; 
						end
					end
					`INST_OR : begin
						wdata_o = op1_or_op2;
						waddr_o = reg_waddr_i;
						wen_o  	= 1'b1; 
					end
					`INST_AND : begin
						wdata_o = op1_and_op2;
						waddr_o = reg_waddr_i;
						wen_o  	= 1'b1; 
					end

					default : begin
						wen_o  = 1'b0;	
						wdata_o = `REG_ZERO;
						waddr_o = `REG_X0_ADDR;	
					end
				endcase
			end
			else if(funct7 == 7'b0000001) begin
				jump_en_o = 1'b0;
				hold_flag_o = 1'b0;
				jump_addr_o = `INST_ZERO_ADDR;
				mem_wreq_o = 1'b0;
				mem_wsel_o	= 4'h0;
				// mem_raddr_o = `ZeroWord;
				mem_waddr_o = `MEM_ZERO_ADDR;
				mem_wdata_o = `MEM_ZERO_DATA;
				case (funct3)
					`INST_MUL : begin
						waddr_o = reg_waddr_i;
						wdata_o = mul_temp[31:0];
						wen_o  	= 1'b1; 
					end
					`INST_MULH: begin
						waddr_o = reg_waddr_i;
						wen_o  	= 1'b1; 
						case ({op1_i[31], op2_i[31]})
							2'b00: begin
								wdata_o = mul_temp[63:32];
							end
							2'b11: begin
								wdata_o = mul_temp[63:32];
							end
							2'b10: begin
								wdata_o = mul_temp_invert[63:32];
							end
							default: begin
								wdata_o = mul_temp_invert[63:32];
							end
						endcase
					end
					`INST_MULHSU: begin
						wen_o  	= 1'b1; 
						waddr_o = reg_waddr_i;
						if (op1_i[31] == 1'b1) begin
							wdata_o = mul_temp_invert[63:32];
						end else begin
							wdata_o = mul_temp[63:32];
						end
					end
					`INST_MULHU : begin
						wen_o  	= 1'b1; 
						waddr_o = reg_waddr_i;
						wdata_o = mul_temp[63:32];
					end
					default: begin
						wen_o  = 1'b0;	
						wdata_o = `REG_ZERO;
						waddr_o = `REG_X0_ADDR;	
					end
				endcase
			end
			else begin
				jump_en_o = 1'b0;
				hold_flag_o = 1'b0;
				jump_addr_o = `INST_ZERO_ADDR;
				mem_wreq_o = 1'b0;
				mem_wsel_o	= 4'h0;
				// mem_raddr_o = `ZeroWord;
				mem_waddr_o = `MEM_ZERO_ADDR;
				mem_wdata_o = `MEM_ZERO_DATA;
				wen_o  = 1'b0;	
				wdata_o = `REG_ZERO;
				waddr_o = `REG_X0_ADDR;	
			end
		end
		`INST_TYPE_L : begin
			jump_addr_o = 32'b0;
			jump_en_o	= 1'b0;
			hold_flag_o = 1'b0;
			mem_wreq_o	= 1'b0;
			mem_wsel_o	= 4'h0;
			mem_waddr_o	= `MEM_ZERO_ADDR;
			mem_wdata_o = `MEM_ZERO_DATA;				
			case(funct3)
				`INST_LB : begin			
					waddr_o = reg_waddr_i;
					wen_o  = 1'b1;
					case(load_index)
						2'b00 : begin
							wdata_o = {{24{mem_rdata_i[7]}}, mem_rdata_i[7:0]};	
						end
						2'b01 : begin
							wdata_o = {{24{mem_rdata_i[15]}}, mem_rdata_i[15:8]};
						end
						2'b10 : begin
							wdata_o = {{24{mem_rdata_i[23]}}, mem_rdata_i[23:16]};
						end
						2'b11 : begin
							wdata_o = {{24{mem_rdata_i[31]}}, mem_rdata_i[31:24]};
						end

						default : begin
							wdata_o = 32'b0;
						end
					endcase
				end
				`INST_LH : begin			
					waddr_o = reg_waddr_i;
					wen_o  = 1'b1;
					case(load_index[1])
						1'b0:begin
							wdata_o = {{16{mem_rdata_i[15]}},mem_rdata_i[15:0]};	
						end
						1'b1:begin
							wdata_o = {{16{mem_rdata_i[31]}},mem_rdata_i[31:16]};
						end
						default:begin
							wdata_o = 32'b0;
						end
					endcase
				end
				`INST_LW : begin
					wdata_o = mem_rdata_i;
					waddr_o = reg_waddr_i;
					wen_o  = 1'b1;						
				end
				`INST_LBU:begin			
					waddr_o = reg_waddr_i;
					wen_o  = 1'b1;
					case(load_index)
						2'b00:begin
							wdata_o = {24'b0, mem_rdata_i[7:0]};	
						end
						2'b01:begin
							wdata_o = {24'b0, mem_rdata_i[15:8]};
						end
						2'b10:begin
							wdata_o = {24'b0, mem_rdata_i[23:16]};
						end
						2'b11:begin
							wdata_o = {24'b0, mem_rdata_i[31:24]};
						end
						default:begin
							wdata_o = 32'b0;
						end
					endcase
				end		
				`INST_LHU:begin			
					waddr_o = reg_waddr_i;
					wen_o  = 1'b1;
					case(load_index[1]) //二字节对齐 所以是第 1 位,第零位默认为0的，其实这里要做处理，
						1'b0:begin		//如果发现最低为不为零要进行硬件报异常(说明你程序员的代码没有二字节对齐，16位对齐)
							wdata_o = {16'b0, mem_rdata_i[15:0]};	
						end
						1'b1:begin
							wdata_o = {16'b0, mem_rdata_i[31:16]};
						end
						default:begin
							wdata_o = 32'b0;
						end
					endcase
				end	
						
				default:begin
					wdata_o = 32'b0;
					waddr_o = 5'b0;
					wen_o  = 1'b0;						
				end
			endcase
		end
		`INST_TYPE_I : begin
			jump_addr_o = `INST_ZERO_ADDR;
			jump_en_o	= 1'b0;
			hold_flag_o = 1'b0;
			mem_wreq_o	= 1'b0;
			mem_wsel_o	= 4'h0;
			mem_waddr_o	= `MEM_ZERO_ADDR;
			mem_wdata_o = `MEM_ZERO_DATA;	
			case(funct3)				
				`INST_ADDI : begin
					wdata_o = op1_add_op2;
					waddr_o = reg_waddr_i;
					wen_o  = 1'b1;
				end
				`INST_SLTI : begin
					wdata_o = {30'b0, op1_less_op2_signed};
					waddr_o = reg_waddr_i;
					wen_o 	= 1'b1;
				end
				`INST_SLTIU : begin
					wdata_o = {30'b0, op1_less_op2_unsigned};
					waddr_o = reg_waddr_i;
					wen_o 	= 1'b1;
				end
				`INST_XORI : begin
					wdata_o = op1_xor_op2;
					waddr_o = reg_waddr_i;
					wen_o 	= 1'b1;
				end
				`INST_ORI : begin
					wdata_o = op1_or_op2;
					waddr_o = reg_waddr_i;
					wen_o 	= 1'b1;
				end
				`INST_ANDI : begin
					wdata_o = op1_and_op2;
					waddr_o = reg_waddr_i;
					wen_o 	= 1'b1;
				end
				`INST_SLLI : begin
					wdata_o = op1_shift_left_op2;
					waddr_o = reg_waddr_i;
					wen_o 	= 1'b1;
				end
				`INST_SRLI, `INST_SRAI : begin
					if(funct7 == 7'b0000000) begin // SRLI
						wdata_o = op1_shift_right_op2;
						waddr_o = reg_waddr_i;
						wen_o 	= 1'b1;							
					end
					else begin // SRAI
						wdata_o = ((op1_shift_right_op2) & SRA_mask) | ({32{op1_i[31]}} & (~SRA_mask));
						waddr_o = reg_waddr_i;
						wen_o 	= 1'b1;							
					end
				end

				default : begin
					wdata_o = `REG_ZERO;
					waddr_o = `REG_X0_ADDR;
					wen_o  = 1'b0;	
				end						
			endcase
		end
		`INST_TYPE_S  :begin
			jump_addr_o = 32'b0;
			jump_en_o	= 1'b0;
			hold_flag_o = 1'b0;
			wdata_o   = 32'b0;
			waddr_o   = 5'b0;
			wen_o    = 1'b0;				
			case(funct3)
				`INST_SB : begin			
					mem_wreq_o  = 1'b1;
					mem_waddr_o = addr_add_addr_offset;
					case(store_index)
						2'b00 : begin
							mem_wdata_o = {24'b0, op2_i[7:0]};
							mem_wsel_o  = 4'b0001;	
						end
						2'b01 : begin
							mem_wdata_o = {16'b0, op2_i[7:0], 8'b0};
							mem_wsel_o  = 4'b0010;	
						end
						2'b10 : begin
							mem_wdata_o = {8'b0, op2_i[7:0], 16'b0};
							mem_wsel_o  = 4'b0100;	
						end
						2'b11 : begin
							mem_wdata_o = {op2_i[7:0], 24'b0};
							mem_wsel_o  = 4'b1000;	
						end

						default : begin
							mem_wdata_o = 32'b0;
							mem_wsel_o  = 4'b0000;	
						end
					endcase
				end
				`INST_SH : begin			
					mem_wreq_o  = 1'b1;
					mem_waddr_o = addr_add_addr_offset;
					case(store_index)
						1'b0 : begin
							mem_wdata_o = {16'b0, op2_i[15:0]};
							mem_wsel_o  = 4'b0011;	
						end
						1'b1 : begin
							mem_wdata_o = {op2_i[15:0], 16'b0};
							mem_wsel_o  = 4'b1100;	
						end

						default : begin
							mem_wdata_o = 32'b0;
							mem_wsel_o  = 4'b0000;	
						end
					endcase
				end	
				`INST_SW : begin
					mem_wreq_o  = 1'b1;
					mem_wsel_o  = 4'b1111;
					mem_waddr_o = addr_add_addr_offset;
					mem_wdata_o = op2_i;						
				end
								
				default : begin
					mem_wreq_o  = 1'b0;
					mem_wsel_o  = 4'b0;
					mem_waddr_o = 32'b0;
					mem_wdata_o = 32'b0;						
				end
			endcase
		end			
		`INST_TYPE_B : begin
			wdata_o = `REG_ZERO;
			waddr_o = `REG_X0_ADDR;
			wen_o  = 1'b0;
			mem_wreq_o	= 1'b0;
			mem_wsel_o	= 4'h0;
			mem_waddr_o	= `MEM_ZERO_ADDR;
			mem_wdata_o = `MEM_ZERO_DATA;
			case(funct3)
				`INST_BEQ : begin
					jump_addr_o = addr_add_addr_offset; 
					jump_en_o	= op1_equal_op2;
					hold_flag_o = 1'b0;					
				end					
				`INST_BNE : begin
					jump_addr_o = addr_add_addr_offset;
					jump_en_o	= ~op1_equal_op2;
					hold_flag_o = 1'b0;					
				end
				`INST_BLT : begin
					jump_addr_o = addr_add_addr_offset;
					jump_en_o	= op1_less_op2_signed;
					hold_flag_o = 1'b0;	
				end
				`INST_BGE : begin
					jump_addr_o = addr_add_addr_offset;
					jump_en_o	= ~op1_less_op2_signed;
					hold_flag_o = 1'b0;	
				end
				`INST_BLTU : begin
					jump_addr_o = addr_add_addr_offset;
					jump_en_o	= op1_less_op2_unsigned;
					hold_flag_o = 1'b0;	
				end
				`INST_BGEU : begin
					jump_addr_o = addr_add_addr_offset;
					jump_en_o	= ~op1_less_op2_unsigned;
					hold_flag_o = 1'b0;	
				end 

				default : begin
					jump_addr_o = `INST_ZERO_ADDR;
					jump_en_o	= 1'b0;
					hold_flag_o = 1'b0;					
				end
			endcase
		end
		`INST_LUI : begin
			wdata_o = op1_i;
			waddr_o = reg_waddr_i;
			wen_o  = 1'b1;
			jump_addr_o = `INST_ZERO_ADDR;
			jump_en_o	= 1'b0;
			hold_flag_o = 1'b0;			
		end
		`INST_AUIPC : begin
			wdata_o = op1_add_op2;
			waddr_o = reg_waddr_i;
			wen_o  = 1'b1;
			jump_addr_o = `INST_ZERO_ADDR;
			jump_en_o	= 1'b0;
			hold_flag_o = 1'b0;	
		end
		`INST_JAL : begin
			wdata_o 	= op1_add_op2;
			waddr_o 	= reg_waddr_i;
			wen_o  		= 1'b1;
			jump_addr_o = addr_add_addr_offset;
			jump_en_o	= 1'b1;
			hold_flag_o = 1'b0;				
		end
		`INST_JALR : begin
			wdata_o 	= op1_add_op2;
			waddr_o 	= reg_waddr_i;
			wen_o  		= 1'b1;
			jump_addr_o = addr_add_addr_offset;
			jump_en_o	= 1'b1;
			hold_flag_o = 1'b0;	
		end

		default : begin
			wdata_o = `REG_ZERO;
			waddr_o = `REG_X0_ADDR;
			wen_o  = 1'b0;
			jump_addr_o = `INST_ZERO_ADDR;
			jump_en_o	= 1'b0;
			hold_flag_o = 1'b0;				
		end
	endcase
end

endmodule


`endif // EX_V